package devyouk.util.excel;

import com.google.common.base.Strings;
import devyouk.util.excel.cellAdapter.DefaultCellValueAdapter;
import devyouk.util.excel.listener.DefaultDataCellListener;
import devyouk.util.excel.listener.DefaultDataStyleListener;
import devyouk.util.excel.listener.DefaultHeadStyleListener;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class ExcelBuilder {

    private BuildConfig buildConfig;

    private OnCreateStyle onCreateHeadStyleListener = null;
    private OnCreateStyle onCreateDataStyleListener = null;
    private OnCreateCell onCreateDataCellListener = null;
    private OnCreateCell onCreateHeadCellListener = null;
    private OnSettingColumnWidth onSettingColumnWidth = null;
    private OnSettingRowHeight onSettingRowHeight = null;

    public ExcelBuilder config(BuildConfig buildConfig) {
        this.buildConfig = buildConfig;
        return this;
    }

    public Workbook buildXls() {
        checkValid();
        initListeners();
        Workbook workbook = new HSSFWorkbook();
        return actualBuild(workbook);
    }

    private Workbook actualBuild(Workbook workbook) {
        // 修改一下作者
        // 创建sheet
        Sheet sheet = workbook.createSheet();
        workbook.setActiveSheet(workbook.getSheetIndex(sheet));
        // 输出表头
        buildHeader(workbook, sheet, buildConfig.columnConfigs);
        // 输出表体
        if (buildConfig.dataSet != null && buildConfig.dataSet.size() > 0) {
            buildDataSet(workbook, sheet, buildConfig.columnConfigs, buildConfig.dataSet);
        }
        return workbook;
    }

    private void buildHeader(Workbook workbook, Sheet sheet, List<ColumnConfig> columnConfigs) {
        // 判断有多少级表头 多少级就占多少行
        AtomicInteger maxHeadLevel = new AtomicInteger(1);
        ArrayList<ArrayList<HeadLabel>> headCell = new ArrayList<>();
        headCell.add(new ArrayList<>());// 保证有一级表头 index = 0

        // 重新组织表头 添加 span 信息
        initHeadLabels(columnConfigs, maxHeadLevel, headCell);

        HashMap<String, CellStyle> styleMap = new HashMap<>();
        // 准备样式
        prepareStylesAndWriteDefineRow(workbook, sheet, columnConfigs, maxHeadLevel, styleMap);
        // 写入表头数据
        writeHeader(sheet, columnConfigs, maxHeadLevel, headCell, styleMap);
    }

    private void prepareStylesAndWriteDefineRow(Workbook workbook, Sheet sheet, List<ColumnConfig> columnConfigs, AtomicInteger maxHeadLevel, HashMap<String, CellStyle> styleMap) {
        Row colDefineRow = sheet.createRow(maxHeadLevel.get());// 插入一行 column-definition row
        colDefineRow.setZeroHeight(false);
        colDefineRow.setHeight((short) 0);
        for (int i = 0; i < columnConfigs.size(); i++) {
            ColumnConfig columnConfig = columnConfigs.get(i);
            sheet.setColumnWidth(i, onSettingColumnWidth.getColumnWidth(i, columnConfig));
            // 初始化表头样式对象
            CellStyle cellStyle = onCreateHeadStyleListener.onCreate(workbook, columnConfig);
            styleMap.put(columnConfig.getFieldName(), cellStyle);
            // 写入 column-definition 数据列定义
            Cell cell = colDefineRow.createCell(i);
            cell.setCellValue(columnConfig.getFieldName());
        }
    }

    private void writeHeader(Sheet sheet, List<ColumnConfig> columnConfigs, AtomicInteger maxHeadLevel, ArrayList<ArrayList<HeadLabel>> headCell, HashMap<String, CellStyle> styleMap) {
        for (int rowI = 0; rowI < maxHeadLevel.get(); rowI++) {
            Row row = sheet.createRow(rowI);
            row.setHeightInPoints(onSettingRowHeight.getRowHeightInPoints(rowI, columnConfigs, buildConfig.dataSet));
            int currentLevel = maxHeadLevel.get() - rowI; // 当前级别
            ArrayList<HeadLabel> headLabels = headCell.get(currentLevel - 1);
            for (HeadLabel headLabel : headLabels) {
                Cell cell = row.getCell(headLabel.firstCol);
                if (cell != null || headLabel.level != currentLevel && headLabel.hasUpper) {
                    continue;
                }
                cell = row.createCell(headLabel.firstCol);
                // 设置表头样式
                CellStyle style = styleMap.get(headLabel.referredConfig.getFieldName());
                cell.setCellStyle(style);
                cell.setCellValue(headLabel.value);
                int lastRow = maxHeadLevel.get() - headLabel.level;
                // 合并单元格
                if (currentLevel != headLabel.level || headLabel.lastCol > headLabel.firstCol) {
                    CellRangeAddress region = new CellRangeAddress(rowI, lastRow, headLabel.firstCol, headLabel.lastCol);
                    sheet.addMergedRegion(region);
                    RegionUtil.setBorderTop(style.getBorderTop(), region, sheet);
                    RegionUtil.setBorderBottom(style.getBorderBottom(), region, sheet);
                    RegionUtil.setBorderLeft(style.getBorderLeft(), region, sheet);
                    RegionUtil.setBorderRight(style.getBorderRight(), region, sheet);
                }
                onCreateHeadCellListener.onCreate(cell, rowI, headLabel.referredConfig, columnConfigs);
            }
        }
    }

    private void initHeadLabels(List<ColumnConfig> columnConfigs, AtomicInteger maxHeadLevel, ArrayList<ArrayList<HeadLabel>> headCell) {
        for (int i = 0; i < columnConfigs.size(); i++) {
            ColumnConfig columnHead = columnConfigs.get(i);
            String titleName = columnHead.getDisplayHeader();
            String advancedHeader = columnHead.getAdvancedHeader();
            HeadLabel h1 = new HeadLabel();
            h1.value = titleName;
            h1.firstCol = i;
            h1.lastCol = i;
            h1.level = 1;
            h1.referredConfig = columnHead;
            headCell.get(0).add(h1);// 添加1级表头
            if (!Strings.isNullOrEmpty(advancedHeader)) {
                h1.hasUpper = true;
                // 多级表头
                String[] splitHeads = advancedHeader.split(":");
                maxHeadLevel.set(Math.max(maxHeadLevel.get(), splitHeads.length + 1));
                increaseHeadLevel(maxHeadLevel, headCell); // 增加级别
                // 冒号: 之前的字符代表更高级别的表头
                for (int hi = splitHeads.length - 1; hi >= 0; hi--) {
                    int headLevel = hi + 1;
                    String upperHead = splitHeads[hi];
                    HeadLabel e = latelyAddedHead(headLevel, headCell, upperHead);
                    if (e == null) {
                        e = new HeadLabel();
                        e.value = upperHead;
                        e.firstCol = i;
                        e.level = headLevel + 1;
                        e.referredConfig = columnHead;
                        headCell.get(headLevel).add(e);
                    }
                    e.lastCol = i;// 更新最后一次出现的位置
                }
            }
        }
        if (maxHeadLevel.get() > 1) {
            // 由于合并单元格只会保留左上角的数值, 在这里将 一级表头 与最高级表头 追加到一起
            // 这样可以方便单元格合并时保留低级表头数据
            /*
             * ┌───────┬────────┐
             * │       │ header2│
             * │header1├────────┤
             * │       │ header3│
             * └───────┴────────┘
             * 制作这样的单元格时, header1 追加到最高级别表头, 比较方便
             * */
            headCell.get(maxHeadLevel.get() - 1).addAll(headCell.get(0));
        }
    }

    private HeadLabel latelyAddedHead(int headLevel, ArrayList<ArrayList<HeadLabel>> headCell, String upperHead) {
        ArrayList<HeadLabel> headLabels = headCell.get(headLevel);
        if (headLabels.size() == 0) {
            return null;
        }
        HeadLabel headLabel = headLabels.get(headLabels.size() - 1);
        if (headLabel.value.equals(upperHead)) {
            return headLabel;
        }
        return null;
    }

    private void increaseHeadLevel(AtomicInteger maxHeadLevel, ArrayList<ArrayList<HeadLabel>> headCell) {
        if (headCell == null) {
            headCell = new ArrayList<>();
        }
        int size = maxHeadLevel.get() - headCell.size();
        for (int i = 0; i < size; i++) {
            headCell.add(new ArrayList<>());
        }
    }


    private void buildDataSet(Workbook workbook, Sheet sheet, List<ColumnConfig> columnConfigs, List<Map<String, Object>> dataSet) {
        int dataRowStart = sheet.getLastRowNum();
        // 初始化表体样式对象
        HashMap<String, CellStyle> styleMap = new HashMap<>();
        for (ColumnConfig columnConfig : columnConfigs) {
            CellStyle cellStyle = onCreateDataStyleListener.onCreate(workbook, columnConfig);
            styleMap.put(columnConfig.getFieldName(), cellStyle);
        }
        // 输出表体数据
        for (int i = 0; i < dataSet.size(); i++) {
            int rowIndex = dataRowStart + 1 + i;
            Row row = sheet.createRow(rowIndex);
            row.setHeightInPoints(onSettingRowHeight.getRowHeightInPoints(rowIndex, columnConfigs, dataSet));
            for (int colIndex = 0; colIndex < columnConfigs.size(); colIndex++) {
                ColumnConfig headConfig = columnConfigs.get(colIndex);
                Cell cell = row.createCell(colIndex);
                // 设置表体数据样式
                cell.setCellStyle(styleMap.get(headConfig.getFieldName()));
                // 设置表体数据值
                onCreateDataCellListener.onCreate(cell, i, headConfig, dataSet);
            }
        }
    }

    private void checkValid() {
        if (buildConfig.columnConfigs == null || buildConfig.columnConfigs.size() == 0) {
            throw new IllegalArgumentException();
        }
    }

    private void initListeners() {
        if (onCreateHeadStyleListener == null) {
            onCreateHeadStyleListener = new DefaultHeadStyleListener();
        }
        if (onCreateDataStyleListener == null) {
            onCreateDataStyleListener = new DefaultDataStyleListener();
            ((DefaultDataStyleListener) onCreateDataStyleListener).setAdapter(DefaultCellValueAdapter.objectToString());
        }
        if (onCreateDataCellListener == null) {
            onCreateDataCellListener = new DefaultDataCellListener();
            ((DefaultDataCellListener) onCreateDataCellListener).setAdapter(DefaultCellValueAdapter.objectToString());
        }
        if (onCreateHeadCellListener == null) {
            onCreateHeadCellListener = (prepared, dataRowIndex, columnConfig, dataSet) -> {
            };
        }
        if (onSettingColumnWidth == null) {
            onSettingColumnWidth = (colIndex, columnConfig) -> Math.max(columnConfig.getDisplayHeader().getBytes().length * 3 * 256, 20 * 256);
        }
        if (onSettingRowHeight == null) {
            onSettingRowHeight = (excelRowIndex, columnConfigs, dataSet) -> 15;
        }
    }


    public static class BuildConfig {
        private List<Map<String, Object>> dataSet;
        private List<ColumnConfig> columnConfigs;

        public BuildConfig dataSet(List<Map<String, Object>> data) {
            this.dataSet = data;
            return this;
        }

        public BuildConfig columnConfig(List<ColumnConfig> columnConfigs) {
            this.columnConfigs = columnConfigs;
            return this;
        }
    }

    public interface OnCreateStyle {
        @NotNull
        CellStyle onCreate(Workbook workbook, ColumnConfig columnConfig);
    }

    public interface OnCreateCell {
        /**
         * @param cell         按默认规则生成好的 cell
         * @param dataRowIndex 数据行的下标, 这个数据行是指表体的数据行, 并不是excel的行下标.
         *                     考虑到有表头存在, 通常excelRowIndex>=DataRowIndex
         * @param columnConfig 列配置
         */
        void onCreate(Cell cell, int dataRowIndex, ColumnConfig columnConfig, List<?> dataSet);
    }

    public interface OnSettingColumnWidth {
        int getColumnWidth(int colIndex, ColumnConfig columnConfig);
    }

    public interface OnSettingRowHeight {
        float getRowHeightInPoints(int excelRowIndex, List<ColumnConfig> columnConfigs, List<Map<String, Object>> dataSet);
    }

    private class HeadLabel {
        String value;
        int firstCol;
        int lastCol;
        int level;
        boolean hasUpper = false;
        ColumnConfig referredConfig;
    }
}
